#pragma once

#include <cftc/reflect/object.h>
#include "object/objectguid.h"

#define CONTACT_DISTANCE            0.5f
#define INTERACTION_DISTANCE        5.0f
#define ATTACK_DISTANCE             5.0f
#define TRADE_DISTANCE              11.11f                  // max distance for trading
#define MAX_VISIBILITY_DISTANCE     333.0f                  // max distance for visible object show, limited in 333 yards
#define DEFAULT_VISIBILITY_DISTANCE 90.0f                   // default visible distance, 90 yards on continents
#define DEFAULT_VISIBILITY_INSTANCE 120.0f                  // default visible distance in instances, 120 yards
#define DEFAULT_VISIBILITY_BGARENAS 180.0f                  // default visible distance in BG/Arenas, 180 yards

#define DEFAULT_WORLD_OBJECT_SIZE   0.388999998569489f      // currently used (correctly?) for any non Unit world objects. This is actually the bounding_radius, like player/creature from creature_model_data
#define DEFAULT_OBJECT_SCALE        1.0f                    // non-Tauren player/item scale as default, npc/go from database, pets from dbc
#define DEFAULT_TAUREN_MALE_SCALE   1.35f                   // Tauren male player scale by default
#define DEFAULT_TAUREN_FEMALE_SCALE 1.25f                   // Tauren female player scale by default

#define MAX_STEALTH_DETECT_RANGE    45.0f

struct Position
{
    Position() : x(0.0f), y(0.0f), z(0.0f), o(0.0f) {}
    float x, y, z, o;
};

namespace battle::object
{

    enum EObjectFields
    {
        OBJECT_FIELD_GUID = 0x00, // Size:2
        OBJECT_FIELD_TYPE = 0x02, // Size:1
        OBJECT_FIELD_ENTRY = 0x03, // Size:1
        OBJECT_FIELD_SCALE_X = 0x04, // Size:1
        OBJECT_FIELD_PADDING = 0x05, // Size:1
        OBJECT_END = 0x06,
    };

    struct WorldLocation
    {
        unsigned int mapid;
        float coord_x;
        float coord_y;
        float coord_z;
        float orientation;
        explicit WorldLocation(unsigned int _mapid = 0, float _x = 0, float _y = 0, float _z = 0, float _o = 0)
            : mapid(_mapid), coord_x(_x), coord_y(_y), coord_z(_z), orientation(_o) {
        }
        WorldLocation(WorldLocation const& loc)
            : mapid(loc.mapid), coord_x(loc.coord_x), coord_y(loc.coord_y), coord_z(loc.coord_z), orientation(loc.orientation) {
        }
    };

    class Player;
    class Creature;
    class Unit;
    class GameObject;
    class Corpse;
    class DynamicObject;
    class Object : public cftc::reflect::Object
    {
        CFT_DECLARE_REFLECT_CLASS();
    public:
        virtual ~Object();

        const bool& IsInWorld() const { return m_inWorld; }
        virtual void AddToWorld()
        {
            if (m_inWorld)
            {
                return;
            }

            m_inWorld = true;

            // synchronize values mirror with values array (changes will send in updatecreate opcode any way
            ClearUpdateMask(false);                         // false - we can't have update data in update queue before adding to world
        }
        virtual void RemoveFromWorld()
        {
            // if we remove from world then sending changes not required
            ClearUpdateMask(true);
            m_inWorld = false;
        }

        ObjectGuid const& GetObjectGuid() const { return GetGuidValue(OBJECT_FIELD_GUID); }
        unsigned int GetGUIDLow() const { return GetObjectGuid().GetCounter(); }
        std::string GetGuidStr() const { return GetObjectGuid().GetString(); }

        unsigned int GetEntry() const { return GetUint32Value(OBJECT_FIELD_ENTRY); }
        void SetEntry(unsigned int entry) { SetUint32Value(OBJECT_FIELD_ENTRY, entry); }

        float GetObjectScale() const
        {
            return m_floatValues[OBJECT_FIELD_SCALE_X] ? m_floatValues[OBJECT_FIELD_SCALE_X] : DEFAULT_OBJECT_SCALE;
        }

        void SetObjectScale(float newScale);

        unsigned char GetTypeId() const { return m_objectTypeId; }
        bool isType(TypeMask mask) const { return (mask & m_objectType); }

#if 0
        virtual void BuildCreateUpdateBlockForPlayer(UpdateData* data, Player* target) const;
#endif
        void SendCreateUpdateToPlayer(Player* player);

        // must be overwrite in appropriate subclasses (WorldObject, Item currently), or will crash
        virtual void AddToClientUpdateList();
        virtual void RemoveFromClientUpdateList();
#if 0
        virtual void BuildUpdateData(UpdateDataMapType& update_players);
#endif
        void MarkForClientUpdate();
        void SendForcedObjectUpdate();

#if 0
        void BuildValuesUpdateBlockForPlayer(UpdateData* data, Player* target) const;
        void BuildOutOfRangeUpdateBlock(UpdateData* data) const;
#endif
        virtual void DestroyForPlayer(Player* target) const;

        const int& GetInt32Value(unsigned short index) const
        {
            CFT_ASSERT(index < m_valuesCount || PrintIndexError(index, false));
            return m_intValues[index];
        }

        const unsigned int& GetUint32Value(unsigned short index) const
        {
            CFT_ASSERT(index < m_valuesCount || PrintIndexError(index, false));
            return m_uint32Values[index];
        }

        const unsigned long long& GetUInt64Value(unsigned short index) const
        {
            CFT_ASSERT(index + 1 < m_valuesCount || PrintIndexError(index, false));
            return *((unsigned long long*)&(m_uint32Values[index]));
        }

        const float& GetFloatValue(unsigned short index) const
        {
            CFT_ASSERT(index < m_valuesCount || PrintIndexError(index, false));
            return m_floatValues[index];
        }

        unsigned char GetByteValue(unsigned short index, unsigned char offset) const
        {
            CFT_ASSERT(index < m_valuesCount || PrintIndexError(index, false));
            CFT_ASSERT(offset < 4);
            return *(((unsigned char*)&m_uint32Values[index]) + offset);
        }

        unsigned short GetUshortValue(unsigned short index, unsigned char offset) const
        {
            CFT_ASSERT(index < m_valuesCount || PrintIndexError(index, false));
            CFT_ASSERT(offset < 2);
            return *(((unsigned short*)&m_uint32Values[index]) + offset);
        }

        ObjectGuid const& GetGuidValue(unsigned short index) const { return *reinterpret_cast<ObjectGuid const*>(&GetUInt64Value(index)); }

        Player* ToPlayer() { if (GetTypeId() == TYPEID_PLAYER) return reinterpret_cast<Player*>(this); else return NULL; }
        Player const* ToPlayer() const { if (GetTypeId() == TYPEID_PLAYER) return reinterpret_cast<Player const*>(this); else return NULL; }

        Creature* ToCreature() { if (GetTypeId() == TYPEID_UNIT) return reinterpret_cast<Creature*>(this); else return NULL; }
        Creature const* ToCreature() const { if (GetTypeId() == TYPEID_UNIT) return reinterpret_cast<Creature const*>(this); else return NULL; }

        Unit* ToUnit() { if (isType(TYPEMASK_UNIT)) return reinterpret_cast<Unit*>(this); else return NULL; }
        Unit const* ToUnit() const { if (isType(TYPEMASK_UNIT)) return reinterpret_cast<Unit const*>(this); else return NULL; }

        GameObject* ToGameObject() { if (GetTypeId() == TYPEID_GAMEOBJECT) return reinterpret_cast<GameObject*>(this); else return NULL; }
        GameObject const* ToGameObject() const { if (GetTypeId() == TYPEID_GAMEOBJECT) return reinterpret_cast<GameObject const*>(this); else return NULL; }

        Corpse* ToCorpse() { if (GetTypeId() == TYPEID_CORPSE) return reinterpret_cast<Corpse*>(this); else return NULL; }
        Corpse const* ToCorpse() const { if (GetTypeId() == TYPEID_CORPSE) return reinterpret_cast<Corpse const*>(this); else return NULL; }

        DynamicObject* ToDynObject() { if (GetTypeId() == TYPEID_DYNAMICOBJECT) return reinterpret_cast<DynamicObject*>(this); else return NULL; }
        DynamicObject const* ToDynObject() const { if (GetTypeId() == TYPEID_DYNAMICOBJECT) return reinterpret_cast<DynamicObject const*>(this); else return NULL; }

        void SetInt32Value(unsigned short index, int  value);
        void SetUint32Value(unsigned short index, unsigned int  value);
        void UpdateUint32Value(unsigned short index, unsigned int  value);
        void SetUint64Value(unsigned short index, const unsigned long long& value);
        void SetFloatValue(unsigned short index, float   value);
        void SetByteValue(unsigned short index, unsigned char offset, unsigned char value);
        void SetUInt16Value(unsigned short index, unsigned char offset, unsigned short value);
        void SetshortValue(unsigned short index, unsigned char offset, short value) { SetUInt16Value(index, offset, (unsigned short)value); }
        void SetGuidValue(unsigned short index, ObjectGuid const& value) { SetUint64Value(index, value.GetRawValue()); }
        void SetStatFloatValue(unsigned short index, float value);
        void SetStatInt32Value(unsigned short index, int value);
        void ForceValuesUpdateAtIndex(unsigned short index);
        void ApplyModUint32Value(unsigned short index, int val, bool apply);
        void ApplyModInt32Value(unsigned short index, int val, bool apply);
        void ApplyModPositiveFloatValue(unsigned short index, float val, bool apply);
        void ApplyModSignedFloatValue(unsigned short index, float val, bool apply);

        void ApplyPercentModFloatValue(unsigned short index, float val, bool apply)
        {
            val = val != -100.0f ? val : -99.9f;
            SetFloatValue(index, GetFloatValue(index) * (apply ? (100.0f + val) / 100.0f : 100.0f / (100.0f + val)));
        }

        /**
        * method to force the update of a given flag to the client. The method is checking the index before indicating the flags need an update.
        *
        * \param index unsigned short of the flag to be updated.
        */
        void MarkFlagUpdateForClient(unsigned short index);
        void SetFlag(unsigned short index, unsigned int newFlag);
        void RemoveFlag(unsigned short index, unsigned int oldFlag);

        void ToggleFlag(unsigned short index, unsigned int flag)
        {
            if (HasFlag(index, flag))
            {
                RemoveFlag(index, flag);
            }
            else
            {
                SetFlag(index, flag);
            }
        }

        /**
         * Checks if a certain flag is set.
         * @param index The index to check, values may originate from at least \ref EUnitFields
         * @param flag Which flag to check, value may originate from a lot of places, see code
         * for examples of what
         * @return true if the flag is set, false otherwise
         * \todo More info on these flags and where they come from, also, which indexes can be used?
         */
        bool HasFlag(unsigned short index, unsigned int flag) const
        {
            CFT_ASSERT(index < m_valuesCount || PrintIndexError(index, false));
            return (m_uint32Values[index] & flag) != 0;
        }

        void ApplyModFlag(unsigned short index, unsigned int flag, bool apply)
        {
            if (apply)
            {
                SetFlag(index, flag);
            }
            else
            {
                RemoveFlag(index, flag);
            }
        }

        void SetByteFlag(unsigned short index, unsigned char offset, unsigned char newFlag);
        void RemoveByteFlag(unsigned short index, unsigned char offset, unsigned char newFlag);

        void ToggleByteFlag(unsigned short index, unsigned char offset, unsigned char flag)
        {
            if (HasByteFlag(index, offset, flag))
            {
                RemoveByteFlag(index, offset, flag);
            }
            else
            {
                SetByteFlag(index, offset, flag);
            }
        }

        bool HasByteFlag(unsigned short index, unsigned char offset, unsigned char flag) const
        {
            CFT_ASSERT(index < m_valuesCount || PrintIndexError(index, false));
            CFT_ASSERT(offset < 4);
            return (((unsigned char*)&m_uint32Values[index])[offset] & flag) != 0;
        }

        void ApplyModByteFlag(unsigned short index, unsigned char offset, unsigned int flag, bool apply)
        {
            if (apply)
            {
                SetByteFlag(index, offset, flag);
            }
            else
            {
                RemoveByteFlag(index, offset, flag);
            }
        }

        void SetShortFlag(unsigned short index, bool highpart, unsigned short newFlag);
        void RemoveShortFlag(unsigned short index, bool highpart, unsigned short oldFlag);

        void ToggleShortFlag(unsigned short index, bool highpart, unsigned char flag)
        {
            if (HasShortFlag(index, highpart, flag))
            {
                RemoveShortFlag(index, highpart, flag);
            }
            else
            {
                SetShortFlag(index, highpart, flag);
            }
        }

        bool HasShortFlag(unsigned short index, bool highpart, unsigned char flag) const
        {
            CFT_ASSERT(index < m_valuesCount || PrintIndexError(index, false));
            return (((unsigned short*)&m_uint32Values[index])[highpart ? 1 : 0] & flag) != 0;
        }

        void ApplyModShortFlag(unsigned short index, bool highpart, unsigned int flag, bool apply)
        {
            if (apply)
            {
                SetShortFlag(index, highpart, flag);
            }
            else
            {
                RemoveShortFlag(index, highpart, flag);
            }
        }

        void SetFlag64(unsigned short index, unsigned long long newFlag)
        {
            unsigned long long oldval = GetUInt64Value(index);
            unsigned long long newval = oldval | newFlag;
            SetUint64Value(index, newval);
        }

        void RemoveFlag64(unsigned short index, unsigned long long oldFlag)
        {
            unsigned long long oldval = GetUInt64Value(index);
            unsigned long long newval = oldval & ~oldFlag;
            SetUint64Value(index, newval);
        }

        void ToggleFlag64(unsigned short index, unsigned long long flag)
        {
            if (HasFlag64(index, flag))
            {
                RemoveFlag64(index, flag);
            }
            else
            {
                SetFlag64(index, flag);
            }
        }

        bool HasFlag64(unsigned short index, unsigned long long flag) const
        {
            CFT_ASSERT(index < m_valuesCount || PrintIndexError(index, false));
            return (GetUInt64Value(index) & flag) != 0;
        }

        void ApplyModFlag64(unsigned short index, unsigned long long flag, bool apply)
        {
            if (apply)
            {
                SetFlag64(index, flag);
            }
            else
            {
                RemoveFlag64(index, flag);
            }
        }

        void ClearUpdateMask(bool remove);

        bool LoadValues(const char* data);

        unsigned short GetValuesCount() const { return m_valuesCount; }

        virtual bool HasQuest(unsigned int /* quest_id */) const { return false; }
        virtual bool HasInvolvedQuest(unsigned int /* quest_id */) const { return false; }
        void _ReCreate(unsigned int entry);
        void SetAsNewObject(bool isNew) { m_isNewObject = isNew; }

    protected:
        Object();

        void _InitValues();
        void _Create(unsigned int guidlow, unsigned int entry, HighGuid guidhigh);

#if 0
        virtual void _SetUpdateBits(UpdateMask* updateMask, Player* target) const;

        virtual void _SetCreateBits(UpdateMask* updateMask, Player* target) const;
#endif

#if 0
        void BuildMovementUpdate(ByteBuffer* data, unsigned char updateFlags) const;
        void BuildValuesUpdate(unsigned char updatetype, ByteBuffer* data, UpdateMask* updateMask, Player* target) const;
        void BuildUpdateDataForPlayer(Player* pl, UpdateDataMapType& update_players);
#endif

        unsigned short m_objectType = 0;

        unsigned char m_objectTypeId = 0;
        unsigned char m_updateFlag = 0;

        union
        {
            int* m_intValues;
            unsigned int* m_uint32Values;
            float* m_floatValues;
        };

        std::vector<bool> m_changedValues;
        std::map<unsigned int, unsigned int> m_plrSpecificFlags;

        unsigned short m_valuesCount = 0;

        bool m_objectUpdated = false;

    private:
        bool m_inWorld = false;
        bool m_isNewObject = false;

        Object(const Object&);                              // prevent generation copy constructor
        Object& operator=(Object const&);                   // prevent generation assigment operator

    public:
        // for output helpfull error messages from ASSERTs
        bool PrintIndexError(unsigned int index, bool set) const;
        bool PrintEntryError(char const* descr) const;
    };

}
